UAT Test Plan — Bestway CSM API Toolkit
| Field | Value |
|---|
| Version | 1.0 |
| Date | March 5, 2026 |
| Project | Bestway BC Development |
| Environment | Business Central Sandbox |
Overview
This test plan validates the Bestway CSM API Toolkit extension for Microsoft Dynamics 365 Business Central. The extension publishes read-only OData v4 API endpoints under the bestway/csm/v1.0 API group, exposing service order data, fault reporting tables, posted document headers and lines, and document attachments. These endpoints support the CSM integration workflow, the Failed CSM Submissions Resolver, and CMS FAQ Management automated test validation.
The extension includes computed fields that look up data from related tables (Service Item, Fault Area, Symptom Code, Fault Code) and a fileUrls field that returns Record Link URLs as a JSON array. All endpoints reject POST, PATCH, and DELETE requests.
Extension Object Summary
| Object | ID | Extends | Purpose |
|---|
| Page | 56101 | Service Header | Service Order API — read-only service order headers with computed csmQualitySent flag. |
| Page | 56102 | Service Line | Service Order Line API — service order lines with resolved fault descriptions and Service Item lookups. |
| Page | 56103 | Document Attachment | Service Order Attachment API — document attachments with base64-encoded content. |
| Codeunit | 56104 | N/A | CSM API Helper — extracted computed field logic for testability. |
| Page | 56123 | Service Invoice Header | Posted Svc. Invoice Header API — posted invoice headers with fileUrls and subpage expansion. |
| Page | 56121 | Service Invoice Line | Posted Svc. Invoice Line API — posted invoice lines with computed itemNo and salesDate fields. |
Prerequisites
Test Area 1: Service Order API
Endpoint: /serviceOrders with $expand for lines and attachments.
Verify the Service Order API returns correct data, computed fields resolve properly, and subpage expansion works.
TC-1.1: Service Orders Endpoint Returns Data
| Step | Action | Expected Result |
|---|
| 1 | Send GET request to /serviceOrders?$top=5. | Response returns HTTP 200 with an array of up to 5 service order records. |
| 2 | Inspect the response fields for each record. | Each record contains: id, no, customerNo, orderDate, postingDate, csmQualitySent. |
| 3 | Verify the csmQualitySent field for an order known to have a posted invoice with CSM Quality Sent = true. | csmQualitySent = true for that order. The field is computed from the Service Invoice Header table. |
TC-1.2: Service Order Subpage Expansion — Lines and Attachments
| Step | Action | Expected Result |
|---|
| 1 | Send GET request to /serviceOrders({id})?$expand=serviceOrderLines,serviceOrderAttachments for an order with at least one line and one attachment. | Response returns HTTP 200 with the order record. |
| 2 | Inspect the serviceOrderLines array. | Lines are present with fields: id, no, serviceItemNo, itemNo, salesDate, faultAreaCode, faultAreaDescription, symptomCode, symptomCodeDescription, faultCode, faultCodeDescription. |
| 3 | Inspect the serviceOrderAttachments array. | Attachments are present with fields: id, documentNo, fileName, fileExtension, contentBase64. |
TC-1.3: Service Order Line Computed Fields
| Step | Action | Expected Result |
|---|
| 1 | Identify a service order line with a Service Item that has Item No. and Sales Date populated. | A suitable line is identified. |
| 2 | Query the line via /serviceOrders({id})?$expand=serviceOrderLines or directly via /serviceOrderLines. | The line is returned. |
| 3 | Verify itemNo matches the Item No. on the Service Item record, and salesDate matches the Sales Date. | Both computed fields are correctly resolved from the Service Item table. |
| 4 | Verify faultAreaDescription, symptomCodeDescription, and faultCodeDescription are populated for lines with those codes set. | All three description fields are resolved from their respective master tables. |
Endpoint: /postedServiceInvoiceHeaders with fileUrls and $expand for lines.
Verify the Posted Service Invoice Header API returns correct header data, the fileUrls computed field, and subpage expansion to invoice lines.
| Step | Action | Expected Result |
|---|
| 1 | Send GET request to /postedServiceInvoiceHeaders?$top=5. | Response returns HTTP 200 with an array of up to 5 posted invoice header records. |
| 2 | Inspect the response fields. | Each record contains: id, no, customerNo, orderNo, orderDate, postingDate, locationCode, csmQualitySent, fileUrls. |
TC-2.2: fileUrls Field Returns Link-Type Record Links Only
This test requires at least one posted invoice header with Link-type Record Links attached. If possible, also attach a Note-type Record Link to verify it is excluded.
| Step | Action | Expected Result |
|---|
| 1 | Identify a posted invoice header with at least one Link-type Record Link attached. | A suitable header is identified. |
| 2 | Query /postedServiceInvoiceHeaders?$filter=no eq '{invoiceNo}' for that header. | The header is returned with the fileUrls field populated. |
| 3 | Parse the fileUrls JSON array. | The array contains one entry per Link-type Record Link, each with 'url' and 'type' keys. The 'type' value is '1'. |
| 4 | If a Note-type Record Link was attached, verify it is NOT in the fileUrls array. | Only Link-type Record Links appear in fileUrls. Notes are excluded. |
TC-2.3: fileUrls Returns Empty Array When No Links Exist
| Step | Action | Expected Result |
|---|
| 1 | Identify a posted invoice header with no Record Links attached. | A suitable header is identified. |
| 2 | Query /postedServiceInvoiceHeaders?$filter=no eq '{invoiceNo}' for that header. | The header is returned with fileUrls = '[]' (empty JSON array). |
TC-2.4: Subpage Expansion — Posted Invoice Lines
| Step | Action | Expected Result |
|---|
| 1 | Send GET request to /postedServiceInvoiceHeaders({id})?$expand=postedServiceInvoiceLines for a header with at least one line. | Response returns HTTP 200 with the header and an expanded postedServiceInvoiceLines array. |
| 2 | Inspect the lines array. | Lines contain: id, documentNo, lineNo, type, no, description, quantity, unitPrice, lineAmount, serviceItemNo, itemNo, salesDate. |
| 3 | Verify the documentNo on each line matches the header's no field. | All lines reference the correct parent header. |
Test Area 3: Posted Invoice Line API
Endpoint: /postedServiceInvoiceLines with computed itemNo and salesDate.
Verify the Posted Invoice Line API returns correct line data and that the computed itemNo and salesDate fields resolve from the Service Item table.
TC-3.1: Posted Invoice Lines Endpoint Returns Data
| Step | Action | Expected Result |
|---|
| 1 | Send GET request to /postedServiceInvoiceLines?$top=10. | Response returns HTTP 200 with an array of up to 10 posted invoice line records. |
| 2 | Inspect the response fields. | Each record contains: id, documentNo, lineNo, type, no, description, quantity, unitPrice, lineAmount, customerNo, orderNo, postingDate, serviceItemNo, serviceItemSerialNo, itemCategoryCode, faultAreaCode, symptomCode, faultCode, resolutionCode, faultReasonCode, bestwaySerialNo, faultCodeDescription, symptomCodeDescription, faultAreaDescription, itemNo, salesDate. |
TC-3.2: Computed Fields — itemNo and salesDate
| Step | Action | Expected Result |
|---|
| 1 | Identify a posted invoice line with a Service Item that has Item No. and Sales Date populated. | A suitable line is identified (serviceItemNo is not blank). |
| 2 | Query /postedServiceInvoiceLines?$filter=serviceItemNo eq '{serviceItemNo}'. | The line is returned with itemNo and salesDate fields populated. |
| 3 | Cross-reference the values against the Service Item card in BC. | itemNo matches Service Item → Item No. salesDate matches Service Item → Sales Date. |
TC-3.3: Computed Fields — Blank When No Service Item
| Step | Action | Expected Result |
|---|
| 1 | Identify a posted invoice line with a blank Service Item No. (e.g., a G/L Account type line). | A suitable line is identified. |
| 2 | Query that line. | itemNo is blank and salesDate is '0001-01-01' (BC zero date in OData). |
Test Area 4: OData Filters and Query Options
Validate $filter, $top, $select, $orderby, and $count across key endpoints.
Verify that standard OData query options work correctly on the CSM API endpoints.
TC-4.1: $filter by Field Values
| Step | Action | Expected Result |
|---|
| 1 | Send GET /postedServiceInvoiceHeaders?$filter=csmQualitySent eq false and locationCode eq 'CS'. | Response returns only headers where CSM Quality Sent is false and Location Code is 'CS'. |
| 2 | Send GET /serviceOrders?$filter=customerNo eq '{knownCustomerNo}'. | Response returns only orders for the specified customer. |
| 3 | Send GET /serviceOrders?$filter=orderDate ge 2025-01-01 and orderDate le 2025-12-31. | Response returns only orders within the specified date range. |
TC-4.2: $top, $select, $orderby, and $count
| Step | Action | Expected Result |
|---|
| 1 | Send GET /serviceOrders?$top=3. | Response returns exactly 3 records (or fewer if fewer than 3 exist). |
| 2 | Send GET /serviceOrders?$select=no,customerNo. | Response returns records with only the no and customerNo fields (plus id). |
| 3 | Send GET /serviceOrders?$orderby=orderDate desc. | Response returns records sorted by orderDate in descending order. |
| 4 | Send GET /serviceOrders?$count=true. | Response includes an @odata.count field with the total record count. |
Test Area 5: Error Handling and Edge Cases
Verify the API behaves correctly for invalid inputs, non-existent records, and read-only enforcement.
These tests confirm the API returns appropriate responses for boundary conditions and rejects data modification attempts.
TC-5.1: Non-Existent Record Returns 404
| Step | Action | Expected Result |
|---|
| 1 | Send GET /serviceOrders(00000000-0000-0000-0000-000000000000). | Response returns HTTP 404 (Not Found). |
| 2 | Send GET /postedServiceInvoiceHeaders(00000000-0000-0000-0000-000000000000). | Response returns HTTP 404 (Not Found). |
TC-5.2: Empty Result Sets Return Empty Arrays
| Step | Action | Expected Result |
|---|
| 1 | Send GET /serviceOrders?$filter=no eq 'NONEXISTENT-ORDER-999'. | Response returns HTTP 200 with an empty 'value' array. |
| 2 | Send GET /postedServiceInvoiceLines?$filter=documentNo eq 'NONEXISTENT-INV-999'. | Response returns HTTP 200 with an empty 'value' array. |
TC-5.3: Write Operations Are Rejected
| Step | Action | Expected Result |
|---|
| 1 | Send POST /serviceOrders with a JSON body containing a minimal service order. | Response returns HTTP 405 (Method Not Allowed) or HTTP 400. The record is not created. |
| 2 | Send PATCH /serviceOrders({id}) with a field update. | Response returns HTTP 405 (Method Not Allowed) or HTTP 400. The record is not modified. |
| 3 | Send DELETE /serviceOrders({id}). | Response returns HTTP 405 (Method Not Allowed) or HTTP 400. The record is not deleted. |